/*
 * Design to read and writer data include all the format data
 */
Tokenizer = function( fstream ){
	fstream = fstream.replace(/\/\/.*$/mg,'' );// (//..);
	this.tokens = fstream.match(/[^\s\n\r"]+/mg );
	this.offset = 0;
};

Tokenizer.prototype.EOF = function()
{
	if( this.tokens == null ) { return true; }
	var token = this.tokens[this.offset];
	while( token == '' && this.offset < this.token.length ){
		this.offset++;
		token = this.tokens[this.offset];
	}
	return this.offset >= this.tokens.length;
};

Tokenizer.prototype.next = function()
{
	if(this.tokens == null ){ return; }
	var token = '';
	while( token == '' && this.offset < this.tokens.length )
	{
		token = this.tokens[this.offset++];
	}
	return token;
};

Tokenizer.prototype.prev = function()
{
	if(this.tokens == null ){ return; }
	var token = '';
	while( token == '' && this.offset >= 0 )
	{
		token = this.tokens[this.offset--];
	}
	return token;
};

ShaderBuilder = function()
{
	this.attribs  = {};
	this.varyings = {};
	this.uniforms = {};
	
	this.functions  = {};
	this.statements = [];
};

ShaderBuilder.prototype.addAttribs = function( attribs )
{
	for( var name in attribs )
	{
		this.attribs[name] = 'attribute ' + attribs[name] + ' ' + name +';';
	}
};

ShaderBuilder.prototype.addVaryings = function( varyings )
{
	for( var name in varyings )
	{
		this.varyings[name] = 'varying ' + varyings[name] + ' ' + name + ';';
	}
};

ShaderBuilder.prototype.addUniforms = function( uniforms )
{
	for( var name in uniforms)
	{
		this.uniforms[name] = 'uniform ' + uniforms[name] + ' ' + name +';';
	}
};

ShaderBuilder.prototype.addFunction = function( name, lines )
{
	this.functions[name] = lines.jion('\n');
};

ShaderBuilder.prototype.addLines = function( statements )
{
	for( var i = 0; i < statements.length; ++i )
	{
		this.statements.push(statements[i]);
	}
};

ShaderBuilder.prototype.getSource = function()
{
	var src = '\
#ifdef GL_ES \n\
precision highp float; \n\
#endif \n';
	
	for( var i in this.attribs ){
		src += this.attribs[i] + '\n';
	}
	
	for( var i in this.varyings ){
		src += this.varyings[i] + '\n';
	}
	
	for( var i in this.uniforms ){
		src += this.uniforms[i] + '\n';
	}
	
	for( var i in this.functions ){
		src += this.functions[i] + '\n';
	}
	
	src += 'void main(void) {\n\t';
	src += this.statements.join('\n\t');
	src += '\n}\n';
	return src;
};

ShaderBuilder.prototype.addWaveform = function( name, wf, timeVar )
{
	if( !wf ){
		this.statements.push('float ' + name + ' = 0.0;');
	}
	
	if( !timeVar ){
		timeVar = 'time';
	}
	
	if( typeof(wf.phase) == "number" )
	{
		wf.phase = wf.phase.toFixed(4);
	}
	
	switch( wf.funcName )
	{
		case 'sin':
			this.statements.push('float ' + name + ' = ' + wf.base.toFixed(4) + ' + sin((' + wf.phase + ' + ' + timeVar + ' * ' + wf.freq.toFixed(4) + ') * 6.283) *' + wf.amp.toFixed(4) + ';') ;
			return;
		case 'square' :
			funcName = 'square';
			this.addSquareFunc();
			break;
		case 'triangle':
			funcName = 'triangle';
			this.addTriangleFunc();
			break;
		case 'sawtooth':
			funcName = 'fract';
			break;
		case 'inversesawtooth':
			funcName = '1.0 -fract';
			break;
		default:
			this.statements.push('float ' + name + ' = 0.0;' );
			return;
	}
	this.statements.push('float ' + name + ' = ' + wf.base.toFixed(4) + ' + ' + funcName + '(' + wf.phase + ' + ' + timeVar + ' * ' + wf.freq.toFixed(4) + ') * ' + wf.amp.toFixed(4) + ';' );
};

ShaderBuilder.prototype.addSquareFunc = function() {
	this.addFunction('square', [
	     'float square(float val) {',
	     ' return ( mod(floor(val * 2.0) + 1.0, 2.0 ) * 2.0)-1.0;',
	     '}',
	     ]);
};
ShaderBuilder.prototype.addTriangleFunc = function(){
	this.addFunction('triangle', [
	     'float triangle( float val ) {',
	     ' return abs(2.0 * fract(val) - 1.0);',
	     '}',
	    ]);
};


slMaterial = {};


slMaterial.loadList = function( source, onload )
{
	for( var i = 0; i < source.length; ++i )
	{
		slMaterial.load(source[i], onload);
	}
};

slMaterial.load = function( url, onload )
{
	var request = new XMLHttpRequest();
	request.onreadystatechange = function(){
		if(request.readyState == 4 && request.status == 200 )
		{
			slMaterial.parse( url, request.responseText, onload );
		}
	};
	
	request.open('GET', url, true );
	request.setRequestHeader('Content-Type', 'text/plain');
	request.send(null);
};

slMaterial.parse = function( url, fstream, onload )
{
	var shaders = new Array();
	var tokens = new Tokenizer( fstream );
	
	while( !tokens.EOF()) 
	{
		var name = tokens.next();
		var shader = slMaterial.parseShader(name, tokens );
		
		if( shader )
		{
			shader.url = url;
			if( shader.stages )
			{
				for( var i = 0; i < shader.stages.length; ++i )
				{
					shader.stages[i].shaderSrc = slMaterial.buildShaderSource( shader, shader.stages[i] );
				}
			}
		}
		shaders.push( shader );
	}
	
	onload(shaders);
};

slMaterial.parseShader = function( name, tokens )
{
	var brace = tokens.next();
	if( brace != '{')
		return null;
	var shader = {
		name : name,
		cull : 'back',
		blend: false,
		opaque: false,
		sort  : 0,
		vertexDeforms:[],// wave normal bulge
		stages:[]
	};
	// parse the shader
	while( !tokens.EOF() )
	{
		var token = tokens.next().toLowerCase();
		if( token == '}' ) { break;}
		
		switch( token )
		{
			case '{':
			{
				var stage = slMaterial.parseStage( tokens );
				shader.stages.push( stage );
				break;
			}
			case 'cull':
			{
				shader.cull = tokens.next();
				break;
			}
			case 'deformvertexes':
			{
				var deform = {
						type: tokens.next().toLowerCase()
				};
				
				switch( deform.type )
				{
					case 'wave':
						deform.spread = 1.0 / parseFloat( tokens.next() );
						deform.waveform = slMaterial.parseWaveform( tokens );
						break;
					default:
						deform = null;
						break;
				}
				
				if( deform ){ shader.vertexDeforms.push( deform ); }
				break;
			}
			
			case 'surfaceparm':
				var param = tokens.next().toLowerCase();
				switch( param )
				{
					case 'sky':
						shader.sky = true;
						break;
					default:
						break;
				}
				break;
			default:
				break;
		}
	}
	return shader;
};

slMaterial.parseStage = function( tokens )
{
	var stage = {
			map   : null,
			clamp : false,
			tcGen : 'base',
			rgaGen : 'identify',
			rgbWaveform : null,
			alphaGen : '1.0',
			alphaFunc : null,
			alphaWaveform : null,
			blendSrc : 'GL_ONE',
			blendDest : 'GL_ZERO',
			hasBlendFunc : false,
			tcMods:[],
			animMaps:[],
			animFreq: 0,
			depthFunc :'lequal',
			depthWrite : true
	};
	
	while( !tokens.EOF() )
	{
		var token = tokens.next();
		if( token == '}' ){ break; }
		
		switch( token )
		{
			case 'clampmap':
				{
					stage.clamp = true;
					break;
				}
			case 'map':
				{
					stage.map = tokens.next();
					break;
				}
			case 'animmap':
				{
					stage.map = 'anim';
					stage.animFreq = parseFloat(tokens.next());
					var nextMap = tokens.next();
					while( nextMap.match(/(\.jpg | \.tga)/))
					{
						stage.animMaps.push(nextMap.replace(/(\.jpg|\.tga)/,'.png'));
						nextMap = tokens.next();
					}
					tokens.prev();
					break;
				}
			case 'rgngen':
				{
					stage.rgbGen = tokens.next().toLowerCase();
					switch( stage.rgbGen )
					{
						case 'wave':
							{
								stage.alphaWaveform = slMaterial.parseWaveform( tokens );
								if( !stage.alphaWaveform ) { stage.rgbGen == '1.0';} 
								break;
							}
						default:
							break;
					}
				}
			case 'alphagen':
				{
					stage.alphaGen = tokens.next().toLowerCase();
					switch( stage.alphaGen )
					{
						case 'wave':
							stage.alphaWaveform = slMaterial.parseWavaform( tokens );
							if( !stage.alphaWaveform ){ stage.alphaGen == '1.0';}
							break;
						default:
							break;
					}
				}
			case 'blendfunc':
				{
					stage.blendSrc = tokens.next();
					stage.hasBlendFunc = true;
					if( !stage.depthWriteOverride )
					{
						stage.depthWrite = false;
					}
					switch( stage.blendSrc )
					{
						case 'add':
							stage.blendSrc = 'GL_ONE';
							stage.blendDest = 'GL_ONE';
							break;
						case 'blend':
							stage.blendSrc = 'GL_SRC_ALPHA';
							stage.blendDest = 'GL_ONE_MINUS_SRC_APHPA';
							break;
						case 'filter':
							stage.blendSrc = 'GL_DST_COLOR';
							stage.blendDest = 'GL_ZERO';
							break;
						default:
							stage.blendDest = tokens.next();
							break;
					}
					break;
				}
			case 'depthfunc':
				stage.depthFunc = tokens.next().toLowerCase();
				break;
			case 'depthWrite':
				stage.depthWrite = true;
				stage.depthWriteOverride = true;
				break;
			case 'tcmod':
				{
					var tcMod = {
						type: tokens.next().toLowerCase()
					};
					switch( tcMod.type )
					{
						case 'rotate':
							tcMod.angle = parseFloat( token.next() ) * (3.1415/180);
							break;
						case 'scroll':
							tcMod.sSpeed = parseFloat( tokens.next() );
							tcMod.tSpeed = parseFloat( tokens.next() );
							break;
						case 'scale':
							tcMod.scaleX = parseFloat( tokens.next());
							tcMod.scaleY = parseFloat( tokens.next());
							break;
						case 'stretch':
							tcMod.waveform = slMaterial.parseWaveform( tokens );
							if( !tcMode.waveform ){ tcMod.type == null; }
							break;
						case 'turb':
							tcMod.turbulance = {
								base : parseFloat( tokens.next() ),
								amp : parseFloat( tokens.next() ),
								phase : parseFloat( tokens.next() ),
								freq : parseFloat( tokens.next() )
							};
							break;
						default:
							tcMod.type = null;
							break;
						}
						if( tcMod.type )
						{
							stage.tcMods.push( tcMod );
						}
						break;
					}
				case 'tcgen':
					stage.tcGen = token.next();
					break;
				default : break;
			}
	}
	if( stage.blendSrc == 'GL_ONE' && stage.blendDest == 'GL_ZERO')
	{
		stage.hasBlendFunc = false;
		stage.depthWrite = true;
	}
	return stage;
};

slMaterial.parseWaveform = function( tokens )
{
	return {
		funcName : tokens.next().toLowerCase(),
		base: parseFloat( tokens.next() ),
		amp: parseFloat( tokens.next() ),
		phase: parseFloat( tokens.next() ),
		freq : parseFloat( tokens.next() )
	};
};

slMaterial.buildShaderSource = function( shader, stage )
{
	return {
		vertex : slMaterial.buildVertexShader( shader, stage ),
		fragment : slMaterial.buildFragmentShader( shader, stage )
	};
};

slMaterial.buildVertexShader = function( stageShader, stage )
{
	var shader = new ShaderBuilder();
	shader.addAttribs({
		position : 'vec3',
		normal : 'vec3',
		color : 'vec4'
	});
	
	shader.addVaryings({
		vTexCoord:'vec2',
		vColor:'vec4'
	});
	
	shader.addUniforms({
		view : 'mat4',
		projection: 'mat4',
		time:'float'
	});
	
	if( stage.isLightmap )
	{
		shader.addAttribs({ lightCoord:'vec2'});
	}
	else
	{
		shader.addAttribs({ texCoord:'vec2'});
	}
	
	shader.addLines(['vec3 defPosition = position;']);
	
	for( var i = 0; i < stageShader.vertexDeforms.length; ++i )
	{
		var deform = stageShader.vertexDeforms[i];
		switch( deform.type )
		{
			case 'wave':
				var name = 'deform' + i;
				var offname = 'deformoff' + i;
				shader.addLines([
				      'float ' + offname + ' = ( position.x + position.y + position.z) * ' + deform.spread.toFixed(4) + ';'
				                 ]);
				var phase = deform.waveform.phase;
				deform.waveform.phase = phase.toFixed(4) + ' + ' + offname;
				shader.addWaveform( name, deform.waveform );
				deform.waveform.phase = phase;
				
				shader.addLines( ['defPosition += normal *' + name + ';']);
		}
	}
	
	shader.addLines(['vec4 worldPosition = view * vec4(defPosition, 1.0 );']);
	shader.addLines(['vColor = color;']);
	
	if( stage.tcGen == 'enviroment'){
		shader.addLines([
		'vec3 viewer = normalize(-worldPosition.xyz);',
		'float d = dot(normal, viewer);',
		'vec3 reflected = normal*2.0*d - viewer;',
		'vTexCoord = vec2( 0.5, 0.5) + reflected.xy * 0.5;'
		]);}
	else
	{
		if( stage.isLightmap ){
			shader.addLines( ['vTexCoord = lightCoord;']);
		}
		else
		{
			shader.addLines( ['vTexCoord = texCoord;'] );
		}
	}
	
	for( var i = 0; i < stage.tcMods.length; ++i )
	{
		var tcMod = stage.tcMods[i];
		switch( tcMod.type )
		{
			case 'rotate':
				shader.addLines([
				    'float r = ' + tcMod.angle.toFixed(4) + ' * time;',
				    'vTexCoord -= vec2( 0.5, 0.5 );',
				    'vTexCoord = vec3(vTexCoord.s * cos(r) -vTexCoord.t * sin(r), vTexture.t * cos(r) + vTexCoord.s * sin(r);',
				    'vTexCoord += vec2( 0.5, 0.5 );',
				    ]);
				break;
			case 'scale':
				shader.addLines([
				   'vTexCoord  += vec2(' + tcMod.scaleX.toFixed(4) + ', ' + tcMod.scaleY.toFixed(4) + ');'
				                ]);
				break;
			case 'stretch':
				shader.addWaveform('stretchWave', tcMod.waveform );
				shader.addLines([
				    'stretchWave = 1.0/stretchWave;',
				    'vTexCoord *= stretchWave;',
				    'vTexCoord += vec2( 0.5 - (0.5 * stretchWave), 0.5 - ( 0.5 * stretchWave));',
				                 ]);
				break;
			case 'turb':
				var tName = 'turbTime' + i;
				shader.addLines( [
				     'float' + tName + ' = ' + tcMod.turbulance.phase.toFixed(4) + ' +time ' + tcMod.turbulance.frep.toFixed(4) + ';',
				     'vTexCoord.s += sin( ( ( position.x + position.z) * 1.0/128.0 * 0.125 +' + tName + ') * 6.283) * ' + tcMod.turbulance.amp.toFixed(4) + ';',
				     'vTexCoord.t += sin( ( position.y * 1.0/128.0 * 0.125 + ' + tName + ' ) * 6.283) * ' + tcMod.turbulance.amp.toFixed(4) + ';'
						]);
				break;
			default:
				break;
		}
	}
	
	switch(stage.alphaGen) {
		case 'lightingspecular':
			shader.addAttribs({ lightCoord: 'vec2' });
			shader.addVaryings({ vLightCoord: 'vec2' });
			shader.addLines([ 'vLightCoord = lightCoord;' ]);
			break;
		default: 
			break;
	}

	shader.addLines(['gl_Position = projection * worldPosition;']);
	
	return shader.getSource();
};

slMaterial.buildFragmentShader = function( stageShader, stage )
{
	var shader = new ShaderBuilder();
	
	shader.addVaryings({
		vTexCoord : 'vec2',
		vColor : 'vec4'
	});
	
	shader.addUniforms({
		texture : 'sampler2D',
		time : 'float'
	});
	
	shader.addLines(['vec4 texColor = texture2D( texture, vTexCoord );']);
	
	switch( stage.rgbGen )
	{
		case 'vertex':
			shader.addLines(['vec3 rgb = texColor.rgb * vColor.rgb;']);
			break;
		case 'wave':
			shader.addWaveform( 'rgbWave', stage.rgbWaveform );
			shader.addLines(['vec3 rgb = texColor.rgb * rgbWave; ']);
			break;
		default:
			shader.addLines([ 'vec3 rgb = texColor.rgb;']);
			break;
	}
	
	switch( stage.alphaGen )
	{
		case 'wave':
			shader.addWaveform('alpha', stage.alphaWaveform );
			break;
		case 'lightingspecular':
			shader.addUniforms({
				lightmap : 'sampler2D'
			});
			shader.addVaryings({
				vLightCoord: 'vec2',
				vLight: 'float'
			});
			shader.addLines([
				'vec4 light = texture2D(lightmap, vLightCoord.st);',
				'rgb *= light.rgb;',
				'rgb += light.rgb * texColor.a * 0.6;', // This was giving me problems, so I'm ignorning an actual specular calculation for now
				'float alpha = 1.0;'
			]);
			break;
		default: 
			shader.addLines(['float alpha = texColor.a;']);
			break;
	}
	
	if(stage.alphaFunc) {
		switch(stage.alphaFunc) {
			case 'GT0':
				shader.addLines([
					'if(alpha == 0.0) { discard; }'
				]);
				break;
			case 'LT128':
				shader.addLines([
					'if(alpha >= 0.5) { discard; }'
				]);
				break;
			case 'GE128':
				shader.addLines([
					'if(alpha < 0.5) { discard; }'
				]);
				break;
			default: 
				break;
		}
	}
	
	shader.addLines(['gl_FragColor = vec4(rgb, alpha);']);
	
	return shader.getSource();
};

slMaterial.build = function( gl , shader )
{
	var glShader = {
			cull : slMaterial.translateCull( gl, shader.cull ),
			sort : shader.sort,
			sky : shader.sky,
			blend : shader.blend,
			name : shader.name,
			stages:[]
	};
	
	for( var j = 0; j < shader.stages.length; ++j )
	{
		var stage = shader.stages[j];
		var glStage = stage;
		glStage.texture = null;
		glStage.blendSrc = slMaterial.translateBlend( gl, stage.blendSrc );
		glStage.blendDest = slMaterial.translateBlend( gl, stage.blendDest );
		glStage.depthFunc = slMaterial.translateDepthFunc( gl, stage.depthFunc );
		
		glShader.stages.push( glStage );
	}
	return glShader;
};

slMaterial.translateDepthFunc = function(gl, depth) {
	if(!depth) { return gl.LEQUAL; }
	switch(depth.toLowerCase()) {
		case 'gequal': return gl.LEQUAL;
		case 'lequal': return gl.LEQUAL;
		case 'equal': return gl.EQUAL;
		case 'greater': return gl.GREATER;
		case 'less': return gl.LESS;
		default: return gl.LEQUAL;
	}
};

slMaterial.translateCull = function(gl, cull) {
	if(!cull) { return gl.FRONT; }
	switch(cull.toLowerCase()) {
		case 'disable':
		case 'none': return null;
		case 'front': return gl.BACK;
		case 'back':
		default: return gl.FRONT;
	}
};

slMaterial.translateBlend = function(gl, blend) {
	if(!blend) { return gl.ONE; }
	switch(blend.toUpperCase()) {
		case 'GL_ONE': return gl.ONE;
		case 'GL_ZERO': return gl.ZERO;
		case 'GL_DST_COLOR': return gl.DST_COLOR;
		case 'GL_ONE_MINUS_DST_COLOR': return gl.ONE_MINUS_DST_COLOR;
		case 'GL_SRC_ALPHA ': return gl.SRC_ALPHA;
		case 'GL_ONE_MINUS_SRC_ALPHA': return gl.ONE_MINUS_SRC_ALPHA;
		case 'GL_SRC_COLOR': return gl.SRC_COLOR;
		case 'GL_ONE_MINUS_SRC_COLOR': return gl.ONE_MINUS_SRC_COLOR;
		default: return gl.ONE;
	}
};




























